package modeller.databasedesignmodel.servicedemand.transaction.sqlquery.demandFetcher;

import exceptions.NoIndexOnAttributesException;
import modeller.databasedesignmodel.Attribute;
import modeller.databasedesignmodel.DatabaseDesign;
import modeller.databasedesignmodel.query.*;
import modeller.databasedesignmodel.relation.Relation;
import modeller.databasedesignmodel.relation.index.AbstractTableIndex;
import modeller.databasedesignmodel.servicedemand.IServiceDemand;
import modeller.databasedesignmodel.servicedemand.transaction.sqlquery.SQLQueryServiceDemand;
import modeller.databasedesignmodel.transaction.SQLQuery;
import queped.NeptuneHelperFunctions;

import java.util.HashSet;

/**
 * Created by:  Jason Ye
 * Date:        23/03/2012
 * Time:        09:20
 */
public class UniqueUnclusteredTreeIndexCalculatorVisitor extends AbstractIndexedSQLQueryCalculatorVisitor {

    public UniqueUnclusteredTreeIndexCalculatorVisitor(DatabaseDesign design) {
        super(design);
    }

    @Override
    public IServiceDemand calculate(SequentialScanQuery query) {

        // BD (#records per page + R)

        double BD = getBDForQuery(query);

        //assume one relation per query, and required relation is at front of list
        Relation relation = query.getRelation().first;

        try {

            // attempt to get an index which matches this
            AbstractTableIndex index = relation.getIndexWithAttributes(query.getRelation().second);

            //no exception thrown = correct index obtained, calculate using this index
            double R = relation.getRIndexEntryToRowSize(index);
            // num records = page size / row length
            double recordsPerPage = relation.getRowsPerDBPages();

            return new SQLQueryServiceDemand(
                    BD * (R + recordsPerPage),
                    relation
            );

        } catch (NoIndexOnAttributesException e) {
            // index doesn't exist, hence use default heap calculator
            //HeapCalculatorVisitor defaultVisitor = new HeapCalculatorVisitor(design);
            return defaultCalculator.calculate(query);
        }

    }

    @Override
    public IServiceDemand calculate(EqualitySearchQuery query) {

        // D(1+logF(RB))

        //assume one relation per query, and required relation is at front of list
        double D = design.getDAverageIOTime();

        try {
            return new SQLQueryServiceDemand(
                    D * (1 + getLogFRB(query)),
                    query.getRelation().getFirst()
            );
        } catch (NoIndexOnAttributesException e) {
            return defaultCalculator.calculate(query);
        }
    }

    @Override
    public IServiceDemand calculate(EqualityWithRangeSearchQuery query) {

        // D(logF(RB)+#matching records )
        double D = design.getDAverageIOTime();
        //assume one relation per query, and required relation is at front of list
        double logFRB = 0;
        try {
            logFRB = getLogFRB(query);
        } catch (NoIndexOnAttributesException e) {
            return defaultCalculator.calculate(query);
        }
        int numMatchingRows = query.getNumMatchingRows();

        return new SQLQueryServiceDemand(
                D * (logFRB + numMatchingRows),
                query.getRelation().getFirst()
        );
    }

    @Override
    public IServiceDemand calculate(InsertQuery query) {
        // D(3+logF(RB))
        try {
            return getDTimes3LogFRB(query);
        } catch (NoIndexOnAttributesException e) {
            return defaultCalculator.calculate(query);
        }
    }

    @Override
    public IServiceDemand calculate(UpdateDeleteQuery query) {

        // Equality Search + 2D = D(1+logF(RB)) + 2D = D(3+logF(RB))
        try {
            return getDTimes3LogFRB(query);
        } catch (NoIndexOnAttributesException e) {
            return defaultCalculator.calculate(query);
        }

    }

    private double getLogFRB(SQLQuery query) throws NoIndexOnAttributesException {

        Relation relation= query.getRelation().first;
        HashSet<Attribute> atributes = query.getRelation().second;

        AbstractTableIndex index = relation.getIndexWithAttributes(atributes);
        double R = relation.getRIndexEntryToRowSize(index);
        double B = relation.getBNumDbPages();
        // calculating F requires index and fill factor
        double F = relation.getFIndexTreeFanout(index, design.getDBFillFactor());

        return NeptuneHelperFunctions.logBaseArbitrary(F, R * B);


    }

    private SQLQueryServiceDemand getDTimes3LogFRB(SQLQuery query) throws NoIndexOnAttributesException {

        return new SQLQueryServiceDemand(
                design.getDAverageIOTime()
                        * (3 + getLogFRB(query)),
                query.getRelation().getFirst()
        );
    }


}